import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import java.util.ListIterator;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;

/**
 * A simple class that opens a server socket, and prints each message received
 * to the console.
 * @author Graeme Stevenson (graeme.stevenson@ucd.ie)
 * @author Amended by Tom Crowe (09137149@ucdconnect.ie)
 * @tinker Amended by Kevin Lally
 */
public class DutyFreeService implements DutyFreeMsgs{
	
	/**
	 * Accept this many connections. Protection against a SYN attack.
	 */
	private int my_backlog = 5;

	/**
	 * The server socket.
	 */
	private ServerSocket my_serverSocket;
	   
	/**
	 * The default server port.
	 */
	private static final int DEFAULT_PORT = 10985;

	// Vector - because eventually it will be a multi-threaded server.
	private	Vector<DutyFree> DUTYFREEList = null;
   
	/**
	 * The service type you want to register. The first part should be a unique
	 * name to identify your services belonging to your project. The second part
	 * identifies the transport. The last part identifies the domain. Note that
	 * this must end with a period. Service types and transports typically begin
	 * with an underscore (e.g. _tcp or _udp). Each part is separated by a
	 * period. E.g. "printer._tcp.local.".
	 */
	public static final String SERVICE_TYPE = "DutyFree._udp.local.";

	/**
	 * A unique name for the service you want to register; e.g.
	 * "casl_level3_colour".
	 */
	public static final String SERVICE_NAME = "DUTYFREEServices";

	/**
	 * Create the server socket.
	 * @param a_port the port that the server socket should listen on.
	 */
	public DutyFreeService(int a_port) throws IOException {

		// Establish some DUTYFREE obj's - eventually read in from file.
		// Establish some DUTYFREE services - eventually read in from file.
		DutyFree tmp = null;
		DUTYFREEList = new Vector<DutyFree>();
		tmp = new DutyFree(1, "Duty Free Offer 1", "Terminal 1", "S1", "Smirnoff Vodka 1L 20 euro");
	
		DUTYFREEList.add(tmp);
		
		tmp = new DutyFree(2, "Duty Free Offer 2", "Terminal 1", "CK1", "Calvin Klien for Men 40 euro");

		DUTYFREEList.add(tmp);
		
	
		try {
			my_serverSocket = new ServerSocket(a_port, my_backlog);
			System.out.println("TCP socket listening on port " + a_port);
		} catch (SecurityException se) {
			se.printStackTrace();
		}
	}

	/**
	 * Listens on the server socket forever and prints each incoming message to the console.
	 * Each connection is dealt with sequentially before the next connection is accepted.
	 */
	public void listen() {
		while (true) {
			Socket socket = null;
			BufferedReader in = null;
			
			try {
				
				// Listens for a connection to be made to this socket.
				socket = my_serverSocket.accept();

				// Wrap a buffered reader round the raw socket input stream.
				// Then read in the message and print the message to the console				
				in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				String msg = in.readLine();
				System.out.println("Received message: " + msg);

				// Parse to get msg type and possibly DUTYFREE ref
				String msgType = null;
				int offIndex = 0;
				int offRef = 0;
				if ((offIndex = msg.indexOf(DUTYFREE_MSG_DELIM)) != -1){
					msgType = msg.substring(0, offIndex);
					if (offIndex != msg.length()-1){
						String refStr = msg.substring(offIndex + 1,msg.length() - 1); // last char should be delimiter also
						try {
							offRef = Integer.valueOf(refStr);
						} 
						catch (NumberFormatException nfe){continue;}
					}
				}else 
					continue;
				
				// Only three messages are allowed. Anything else and the connection is terminated straight away.
				if (msgType.compareTo(DUTYFREE_GET_LIST_MSG) == 0) {
					String reply = processGetList();
					sendReply(reply, socket);
				}
				else if (msgType.compareTo(DUTYFREE_GET_INFO_MSG) == 0){
					String reply = processGetInfo(offRef);
					if (reply != null)
						sendReply(reply, socket);					
				}
				else if (msgType.compareTo(DUTYFREE_GET_MAP_MSG) == 0){
					String reply = processGetMap(offRef);
					if (reply != null)
						sendReply(reply, socket);					
				}
			}
			catch (IOException ioe) {
				ioe.printStackTrace();
			} 
			catch (SecurityException se) {
				se.printStackTrace();
			}
			finally {
				try {
					in.close();
					socket.close();
					System.out.println("Have executed finally.");
				} catch (IOException ioe) {
					ioe.printStackTrace();
				}
			}
		}
	}

	
	private String processGetList() {

		StringBuilder resp = new StringBuilder();
		DutyFree currOff = null;
		ListIterator<DutyFree> iter = DUTYFREEList.listIterator();
		
		// Add the msg header and a count of the number of DUTYFREEs as the first msg field
		resp.append(DUTYFREE_LIST_MSG + DUTYFREE_MSG_DELIM);
		resp.append(DUTYFREEList.size() + DUTYFREE_MSG_DELIM);

		while (iter.hasNext()){
			currOff = iter.next();
			resp.append(currOff.getMainInfoAsDelimString(DUTYFREE_MSG_DELIM));	
		}
		return resp.toString();
	}
	
	
	private String processGetInfo(int offRef) {

		StringBuilder resp = null;
		DutyFree currOff = null;
		boolean found = false;
		ListIterator<DutyFree> iter = DUTYFREEList.listIterator();

		while (iter.hasNext()){
			currOff = iter.next();
			if (offRef == currOff.getRef()){
				found = true;
				break;
			}
		}
		
		if (!found)
			return null;
		
		// Add the msg header, DUTYFREE ref and a count of the number of DUTYFREEs as the first msg field
		resp = new StringBuilder();
		resp.append(DUTYFREE_INFO_MSG + DUTYFREE_MSG_DELIM);
		resp.append(currOff.getRef() + DUTYFREE_MSG_DELIM);
		resp.append(currOff.getOffServices().size() + DUTYFREE_MSG_DELIM);
		resp.append(currOff.getOffServicesDelimString(DUTYFREE_MSG_DELIM));	

		return resp.toString();
	}
	
	private String processGetMap(int offRef){
		return "null";
	}
	
	
	private void sendReply(String retMsg, Socket sock) throws IOException, SecurityException {

		// Send the response back to the client.;
		BufferedWriter out = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
		out.write(retMsg,0,retMsg.length());
		out.newLine();
		out.flush();
		System.out.println("Returned message: " + retMsg);

		// Tidy up
		out.close();

	}
	
	/**
	 * Parses the arguments to the program and create the server socket. Also register
	 * the service (starting a mDNS instance) and then listen for connections on the 
	 * server socket ad infinitum.
	 * @param args the program arguments. Should take the form &lt;port&gt;
	 */
	public static void main(String[] args) throws IOException{
		int port = 0;
		DutyFreeService server = null;

		// Check we have the right number of arguments and parse
		if (args.length == 1) {
			try {
				port = Integer.valueOf(args[0]);
			} catch (NumberFormatException nfe) {
				System.err.println("The value provided for the port is not an integer");
				nfe.printStackTrace();
				System.exit(1);
			}
//			if (port < 1){
//				System.err.println("The values provided for the port must be > 0");
//				System.exit(1);
//			}
		}
		if (args.length == 0)
			port = DEFAULT_PORT;
		else {
			System.out.println("Usage: java DUTYFREEServices <port (optional)>");
			System.out.println("Default port: " + DEFAULT_PORT);
		}
			
		// Create the server before registering service in case there is a port clash for my server
		try {
			server = new DutyFreeService(port);
			port = server.my_serverSocket.getLocalPort();
		} catch (IOException ioe) {
			System.err.println("The server could not be started - possibly port clash on port: " + port);
			ioe.printStackTrace();
			System.exit(1);
		}
		
		// Register this service with jmDns (zeroconf impl)
		JmDNS jmdns = JmDNS.create();
		ServiceInfo info = ServiceInfo.create(SERVICE_TYPE, SERVICE_NAME, port, 0, 0,
          						"a_property=some_value");
		jmdns.registerService(info);
		System.out.println("Registered Service as " + info); // note that the
                                                         // service name may
                                                         // have changed if a
                                                         // service with that
                                                         // name was already
                                                         // registered.

		// Listen on the server socket. This will run until the program is killed.
		server.listen();

		// Clean up jmDns registration
		jmdns.unregisterService(info);
		jmdns.close();

		// Clean up server socket
		server.my_serverSocket.close();

	}
} // end class

